Kotlin-Mvvm-DaggerHilt-Retrofit-Download Image Using Url-Save,Delete,View Image-Example

 Kotlin-Mvvm-DaggerHilt-Retrofit-Download Image Using Url-Save,Delete,View Image-Example

Step 1:
Open build.gradle(:Project) and add following dependency and rebuild the project.
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:7.0.4"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20"
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.40.5'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}

task clean(type: Delete) {
delete rootProject.buildDir
}
Step 2:
Open build.gradle(:app) and add following dependency and rebuild the project.
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}
android {
compileSdk 31
defaultConfig {
applicationId "in.kotlinkatta.kotlinkattademo"
minSdk 21
targetSdk 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}

buildFeatures{
dataBinding true;
viewBinding true
}
}

dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
//kotlin Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
//room
implementation "androidx.room:room-ktx:2.4.1"
implementation "androidx.room:room-runtime:2.4.1"
kapt "androidx.room:room-compiler:2.4.1"
//multidex
implementation 'androidx.multidex:multidex:2.0.1'
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0"
// view model ktx
implementation 'androidx.activity:activity-ktx:1.4.0'
//hilt viewmodel
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03'
kapt("org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.3.0")
//retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
//moshi
implementation("com.squareup.moshi:moshi-kotlin:1.12.0")
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
//SharedPreferences
implementation("androidx.datastore:datastore:1.0.0")
implementation("androidx.datastore:datastore-preferences:1.0.0")
//DaggerHilt
implementation 'com.google.dagger:hilt-android:2.40.5'
kapt 'com.google.dagger:hilt-compiler:2.40.5'
}
// Allow references to generated code
kapt {
correctErrorTypes = true
}
Step 3:
Open gradle.properties and add following code and rebuild the project.
kapt.use.worker.api=false
Step 4:
Open AndroidManifest.xml and add following dependency and rebuild the project.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="in.kotlinkatta.kotlinkattademo">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name=".container.BaseApp"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.KotlinKattaDemo">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
App Flow Image:
Step 5:
Create new class BaseApp.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.container

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class BaseApp : Application() {}
Step 6:
Create new interface ApiServices.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.network

import okhttp3.ResponseBody
import retrofit2.http.GET
import retrofit2.http.Url

interface ApiServices {
companion object {
const val BASE_URL = "https://www.zidsworld.com"
}
@GET
suspend fun getDownloadImageDetails(@Url fileUrl: String): ResponseBody
}
Step 7:
Create new class ApiServicesImp.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.network

import okhttp3.ResponseBody
import retrofit2.http.Url
import javax.inject.Inject

class ApiServicesImp @Inject constructor(private val apiServices: ApiServices) {
suspend fun getDownloadImageDetails(@Url fileUrl: String): ResponseBody = apiServices.getDownloadImageDetails(fileUrl)
}
Step 8:
Create new class NetworkHelper.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.network

import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class NetworkHelper @Inject constructor(@ApplicationContext private val context: Context) {
fun isNetworkConnected(): Boolean {
var result = false
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val networkCapabilities = connectivityManager.activeNetwork ?: return false
val activeNetwork =
connectivityManager.getNetworkCapabilities(networkCapabilities) ?: return false
result = when {
activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
else -> false
}
} else {
connectivityManager.run {
connectivityManager.activeNetworkInfo?.run {
result = when (type) {
ConnectivityManager.TYPE_WIFI -> true
ConnectivityManager.TYPE_MOBILE -> true
ConnectivityManager.TYPE_ETHERNET -> true
else -> false
}
}
}
}
return result
}
}
Step 9:
Create new object AppModule.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.di

import `in`.kotlinkatta.kotlinkattademo.network.ApiServices
import `in`.kotlinkatta.kotlinkattademo.network.ApiServicesImp
import `in`.kotlinkatta.kotlinkattademo.repository.ImageDownloadRepository
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun providesMoshi(): Moshi = Moshi
.Builder()
.add(KotlinJsonAdapterFactory())
.build()

@Provides
@Singleton
fun providesApiService(moshi: Moshi): ApiServices = Retrofit
.Builder()
.run {
baseUrl(ApiServices.BASE_URL)
addConverterFactory(MoshiConverterFactory.create(moshi).asLenient())
.build()
}.create(ApiServices::class.java)

@Provides
fun providesImageDownloadRepository(apiServicesImp: ApiServicesImp): ImageDownloadRepository =
ImageDownloadRepository(apiServicesImp)
}
Step 10:
Create new class ImageDownloadRepository.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.repository

import `in`.kotlinkatta.kotlinkattademo.network.ApiServicesImp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import okhttp3.ResponseBody
import retrofit2.http.Url
import javax.inject.Inject

class ImageDownloadRepository @Inject constructor(
private val apiServicesImp: ApiServicesImp
) {
fun downloadImageDetails(@Url fileUrl: String): Flow<ResponseBody> = flow {
emit(apiServicesImp.getDownloadImageDetails(fileUrl))
}.flowOn(Dispatchers.IO)
}
Step 11:
Create new sealed class ApiStateImageDownload.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.apistate

import okhttp3.ResponseBody

sealed class ApiStateImageDownload {
class Success(val data: ResponseBody) : ApiStateImageDownload()
class Failure(val errormsg: Throwable) : ApiStateImageDownload()
object Loading : ApiStateImageDownload()
object Empty : ApiStateImageDownload()
class Message(val msg: String) : ApiStateImageDownload()
}
Step 12:
Create new class ImageDownloadViewModel.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.uiviewmodel

import `in`.kotlinkatta.kotlinkattademo.apistate.ApiStateImageDownload
import `in`.kotlinkatta.kotlinkattademo.network.NetworkHelper
import `in`.kotlinkatta.kotlinkattademo.repository.ImageDownloadRepository
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import retrofit2.http.Url
import javax.inject.Inject

@HiltViewModel
class ImageDownloadViewModel
@Inject
constructor(
private val imageDownloadRepository: ImageDownloadRepository,
private val networkHelper: NetworkHelper
) : ViewModel() {

private val _imageDownloadApiState: MutableStateFlow<ApiStateImageDownload> = MutableStateFlow(
ApiStateImageDownload.Empty
)
val imageDownloadApiState: StateFlow<ApiStateImageDownload> = _imageDownloadApiState

fun postImageDownloadDetails(@Url fileUrl: String) = viewModelScope.launch {
if (networkHelper.isNetworkConnected()) {
imageDownloadRepository.downloadImageDetails(fileUrl)
.onStart {
_imageDownloadApiState.value = ApiStateImageDownload.Loading
}.catch { e ->
_imageDownloadApiState.value = ApiStateImageDownload.Failure(e)
}.collect { data ->
_imageDownloadApiState.value = ApiStateImageDownload.Success(data)
}
} else {
_imageDownloadApiState.value = ApiStateImageDownload.Message("No internet connection")
}
}
}
Step 13:
Open layout xml file activity_main.xml and add following code.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">

<androidx.appcompat.widget.AppCompatButton
android:background="#FF03DAC5"
android:layout_margin="5dp"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:text="Download / Save"
android:id="@+id/btndownloadsave"/>

<androidx.appcompat.widget.AppCompatButton
android:background="#FF03DAC5"
android:layout_margin="5dp"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:text="Display"
android:id="@+id/btndisplay"/>

</LinearLayout>

<androidx.appcompat.widget.AppCompatButton
android:background="#FF03DAC5"
android:layout_margin="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:text="Delete"
android:id="@+id/btndelete"/>

<ProgressBar
android:id="@+id/progressbarProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>

<ImageView
android:id="@+id/imgDisplay"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

</LinearLayout>
Step 14:
Open Activity class MainActivity.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo

import `in`.kotlinkatta.kotlinkattademo.apistate.ApiStateImageDownload
import `in`.kotlinkatta.kotlinkattademo.databinding.ActivityMainBinding
import `in`.kotlinkatta.kotlinkattademo.uiviewmodel.ImageDownloadViewModel
import android.content.ContentValues
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collect
import java.io.IOException
import java.io.OutputStream
import java.util.*

@AndroidEntryPoint
public class MainActivity : AppCompatActivity() {

private lateinit var binding: ActivityMainBinding
private val imageDownloadViewModel: ImageDownloadViewModel by viewModels()
var imageUrl = "/wp-content/uploads/2018/06/cat_1530281469.jpg"
var uri: Uri = Uri.parse("")

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.progressbarProgress.isVisible = false
funAllBtnClick()
}

private fun funAllBtnClick() {
binding.btndownloadsave.setOnClickListener {
funDownloadImageBitmap()
}
binding.btndisplay.setOnClickListener {
try {
funDisplayImage(uri)
} catch (e: Exception) {
Log.d("main", "" + e.message)
}
}
binding.btndelete.setOnClickListener {
try {
val inputStream = contentResolver.openInputStream(uri)
if (inputStream != null) {
inputStream.close()
contentResolver.delete(uri, null, null)
Toast.makeText(
applicationContext,
"Image Deleted Successfully.",
Toast.LENGTH_SHORT
).show()
binding.imgDisplay.setImageURI(null)
}
} catch (e: Exception) {
Toast.makeText(applicationContext, "Image Not Available.", Toast.LENGTH_SHORT)
.show()
}
}
}

private fun funDisplayImage(uriDisplayImage: Uri) {
try {
val inputStream = contentResolver.openInputStream(uri)
if (inputStream != null) {
inputStream.close()
binding.imgDisplay.setImageURI(uriDisplayImage)
}
} catch (e: Exception) {
Toast.makeText(applicationContext, "Image Not Available.", Toast.LENGTH_SHORT)
.show()
}
}

private fun funDownloadImageBitmap() {
binding.apply {
lifecycleScope.launchWhenStarted {

imageDownloadViewModel.postImageDownloadDetails(imageUrl)
imageDownloadViewModel.imageDownloadApiState.collect {
when (it) {
is ApiStateImageDownload.Success -> {
try {
val fileName = Calendar.getInstance().timeInMillis.toString()
progressbarProgress.isVisible = false
imgDisplay.isVisible = true
val bytes: ByteArray = it.data.bytes()
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
saveImage(bitmap, fileName)
} catch (e: Exception) {
}
}
is ApiStateImageDownload.Failure -> {
progressbarProgress.isVisible = false
Toast.makeText(
this@MainActivity,
it.errormsg.toString(),
Toast.LENGTH_SHORT
).show()
}
is ApiStateImageDownload.Loading -> {
progressbarProgress.isVisible = true
}
is ApiStateImageDownload.Empty -> {
progressbarProgress.isVisible = false
}
is ApiStateImageDownload.Message -> {
progressbarProgress.isVisible = false
Toast.makeText(this@MainActivity, it.msg, Toast.LENGTH_SHORT).show()
}
}
}
}
}
}

@Throws(IOException::class)
private fun saveImage(bitmap: Bitmap, name: String) {
val fos: OutputStream
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val resolver = contentResolver
val contentValues = ContentValues()
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name + ".jpg")
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg")
contentValues.put(
MediaStore.MediaColumns.RELATIVE_PATH,
"Android/media/${applicationContext.packageName}/kotlinkatta"
)
val imageUri =
resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
fos = resolver.openOutputStream(Objects.requireNonNull(imageUri)!!)!!
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos)
Objects.requireNonNull<OutputStream?>(fos)
fos.flush();
fos.close();
Toast.makeText(applicationContext, "Image Saved Successfully.", Toast.LENGTH_SHORT).show()

/*Display Image*/
uri = Uri.parse(imageUri.toString())
funDisplayImage(uri)
}
} catch (e: Exception) {
Toast.makeText(applicationContext, e.message, Toast.LENGTH_SHORT).show()
}
}
}
Output:


Comments